FindYourCoffee 專案的需求 :
今天要來在 Google 地圖上顯示咖啡廳標記,然後我們也需要做一些調整 :
GOGO~~~
調整 main_activity.xml,將不必要的 UI 拿掉 :
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="5dp"
    tools:context=".MainActivity">
    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minWidth="50dp"
        android:minHeight="50dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_container"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
接著打開 MainActivity.kt,調整初始化的方法 initView(),拿掉和 RecyclerView 相關的程式碼 :
private fun initView() {
        lifecycleScope.launch(Dispatchers.Main) {
            binding.progressbar.visibility = View.VISIBLE
            viewModel.loadCafes()
        }
        val mapFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }
調整  onMapReady(),將預設地點改為台北 :
// Update the map configuration at runtime.
override fun onMapReady(googleMap: GoogleMap) {
    // return early if the map was not initialised properly
    map = googleMap
    // 取得位置權限
    requestLocationPermissions()
    with(map) {
        // Set the map type
        mapType = com.google.android.gms.maps.GoogleMap.MAP_TYPE_NORMAL
        // UI 設定 : 交通 、指南針、縮放按鈕
        isTrafficEnabled = true
        uiSettings.isCompassEnabled = true
        uiSettings.isZoomControlsEnabled = true
        // 設置預設標記 - 台北
        val taipei = LatLng(25.0329694, 121.5654177) 
        moveCamera(com.google.android.gms.maps.CameraUpdateFactory.newLatLngZoom(taipei, 10f))
        addMarker(
            MarkerOptions().position(taipei).title("台北")
        )
        // 我的位置點擊
        setOnMyLocationButtonClickListener(this@MainActivity)
        setOnMyLocationClickListener(this@MainActivity)
    }
}
執行結果 :

在 onCreate() 內,我們在取得咖啡廳資料的地方取出咖啡廳的經緯度和設定詳細資訊 :
// 取得咖啡廳資料
viewModel.coffeeShopsLiveData.observe(this) { cafes ->
    binding.progressbar.visibility = View.GONE
    cafes.forEach {
        // 取出咖啡廳的經緯度
        placesMap = mutableMapOf(
            it.id to LatLng(it.latitude.toDouble(), it.longitude.toDouble())
        )
        // 設定咖啡廳的詳細資訊
        placeDetailsMap[it.id] = MainActivity.PlaceDetails(
            position = LatLng(it.latitude.toDouble(), it.longitude.toDouble()),
            title = it.name,
            icon = BitmapDescriptorFactory
                .defaultMarker(BitmapDescriptorFactory.HUE_AZURE)
        )
    }
		// ...
}
並依照剛剛設定的詳細資訊,將標記加入至地圖 :
private fun addMarkersToMap() {
    // place markers for each of the defined locations
    placeDetailsMap.keys.map {
        with(placeDetailsMap.getValue(it)) {
            map.addMarker(
                MarkerOptions()
                    .position(position)
                    .title(title)
                    .snippet(snippet)
                    .icon(icon)
                    .infoWindowAnchor(infoWindowAnchorX, infoWindowAnchorY)
                    .draggable(draggable)
                    .zIndex(zIndex))
        }
    }
}
// 取得咖啡廳資料
viewModel.coffeeShopsLiveData.observe(this) { cafes ->
    binding.progressbar.visibility = View.GONE
    cafes.forEach {
        // 取出咖啡廳的經緯度
        placesMap = mutableMapOf(
            it.id to LatLng(it.latitude.toDouble(), it.longitude.toDouble())
        )
        // 設定咖啡廳的詳細資訊
        placeDetailsMap[it.id] = MainActivity.PlaceDetails(
            position = LatLng(it.latitude.toDouble(), it.longitude.toDouble()),
            title = it.name,
            icon = BitmapDescriptorFactory
                .defaultMarker(BitmapDescriptorFactory.HUE_AZURE)
        )
    }
    // 將標記加入至地圖
    addMarkersToMap()
    // ...
}
最後是移動地圖
// 計算縮放級別與中心位置,讓所有咖啡廳皆可見
val boundsBuilder = LatLngBounds.Builder()
placesMap.keys.map { place -> boundsBuilder.include(placesMap.getValue(place)) }
val bounds = boundsBuilder.build()
// 移動地圖
val cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, 100)
map.moveCamera(cameraUpdate)
observe() 完整版 :
// 取得咖啡廳資料
viewModel.coffeeShopsLiveData.observe(this) { cafes ->
    binding.progressbar.visibility = View.GONE
    cafes.forEach {
        // 將咖啡廳的經緯度加入至 Map
        placesMap = mutableMapOf(
            it.id to LatLng(it.latitude.toDouble(), it.longitude.toDouble())
        )
        // 設定咖啡廳的詳細資訊
        placeDetailsMap[it.id] = MainActivity.PlaceDetails(
            position = LatLng(it.latitude.toDouble(), it.longitude.toDouble()),
            title = it.name,
            icon = BitmapDescriptorFactory
                .defaultMarker(BitmapDescriptorFactory.HUE_AZURE)
        )
    }
    // 將標記加入至地圖
    addMarkersToMap()
    // 計算縮放級別與中心位置,讓所有咖啡廳皆可見
    val boundsBuilder = LatLngBounds.Builder()
    placesMap.keys.map { place -> boundsBuilder.include(placesMap.getValue(place)) }
    val bounds = boundsBuilder.build()
    // 移動地圖
    val cameraUpdate = CameraUpdateFactory.newLatLngBounds(bounds, 100)
    map.moveCamera(cameraUpdate)
}
成功顯示台北咖啡廳資訊,但居然有其他縣市的資料混進來了 !? 驚

又減少一個專案需求~~~開心